﻿using System;

namespace Silver3D.Math
{
    public struct Vec4
    {
        public static Vec4 Zero { get { return new Vec4(0.0, 0.0, 0.0, 1.0); } }
        public static Vec4 One { get { return new Vec4(1.0, 1.0, 1.0, 1.0); } }
        public static Vec4 XAxis { get { return new Vec4(1.0, 0.0, 0.0, 1.0); } }
        public static Vec4 YAxis { get { return new Vec4(0.0, 1.0, 0.0, 1.0); } }
        public static Vec4 ZAxis { get { return new Vec4(0.0, 0.0, 1.0, 1.0); } }

        private double x, y, z, w;

        public double X { get { return x; } set { x = value; } }
        public double Y { get { return y; } set { y = value; } }
        public double Z { get { return z; } set { z = value; } }
        public double W { get { return w; } set { w = value; } }

        public double LengthSquared
        {
            get { return Dot(this); }
        }

        public double Length
        {
            get
            {
                double lsq = LengthSquared;
                if (lsq == 0.0) return 0.0;
                return System.Math.Sqrt(lsq);
            }
        }

        public Vec4 Unit
        {
            get { return new Vec4(this) / Length; }
        }

        public Vec4(double xyzw)
        {
            x = y = z = w = xyzw;
        }

        public Vec4(double x, double y, double z)
            : this(x, y, z, 1.0)
        {
        }
        
        public Vec4(double x, double y, double z, double w)
        {
            this.x = x;
            this.y = y;
            this.z = z;
            this.w = w;
        }

        public Vec4(Vec4 copy)
            : this(copy.x, copy.y, copy.z, copy.w)
        {
        }

        public Vec4(Vec2 v2)
            : this(v2.X, v2.Y, 0.0, 1.0)
        {
        }

        public Vec4(Vec3 v3)
            : this(v3.X, v3.Y, v3.Z, 1.0)
        {
        }

        public double this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0: return x;
                    case 1: return y;
                    case 2: return z;
                    case 3: return w;
                    default: throw new ArgumentOutOfRangeException("index");
                }
            }
            set
            {
                switch (index)
                {
                    case 0: x = value; break;
                    case 1: y = value; break;
                    case 2: z = value; break;
                    case 3: w = value; break;
                    default: throw new ArgumentOutOfRangeException("index");
                }
            }
        }


        public static Vec4 operator -(Vec4 v)
        {
            return new Vec4(-v.x, -v.y, -v.z, -v.w);
        }

        public static Vec4 operator -(double lhs, Vec4 rhs)
        {
            return new Vec4(lhs) - rhs;
        }

        public static Vec4 operator -(Vec4 lhs, double rhs)
        {
            return lhs - new Vec4(rhs);
        }

        public static Vec4 operator -(Vec4 lhs, Vec4 rhs)
        {
            return new Vec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w);
        }

        public static Vec4 operator +(Vec4 v)
        {
            return new Vec4(v);
        }

        public static Vec4 operator +(double lhs, Vec4 rhs)
        {
            return new Vec4(lhs) + rhs;
        }

        public static Vec4 operator +(Vec4 lhs, double rhs)
        {
            return lhs + new Vec4(rhs);
        }

        public static Vec4 operator +(Vec4 lhs, Vec4 rhs)
        {
            return new Vec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w);
        }

        public static Vec4 operator *(double s, Vec4 rhs)
        {
            return new Vec4(s) * rhs;
        }

        public static Vec4 operator *(Vec4 lhs, double s)
        {
            return s * lhs;
        }

        public static Vec4 operator *(Vec4 lhs, Vec4 rhs)
        {
            return new Vec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w);
        }

        public static Vec4 operator /(double s, Vec4 rhs)
        {
            double inv = 1.0 / s;
            return inv * rhs;
        }

        public static Vec4 operator /(Vec4 lhs, double s)
        {
            double inv = 1.0 / s;
            return lhs * inv;
        }

        public static Vec4 operator /(Vec4 lhs, Vec4 rhs)
        {
            return new Vec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w);
        }

        public static bool operator ==(Vec4 a, Vec4 b)
        {
            return (a.x == b.x) && (a.y == b.y) && (a.z == b.z) && (a.w == b.w);
        }

        public static bool operator !=(Vec4 a, Vec4 b)
        {
            return (a.x != b.x) || (a.y != b.y) || (a.z != b.z) || (a.w != b.w);
        }

        public static bool operator <(Vec4 a, Vec4 b)
        {
            return a.LengthSquared < b.LengthSquared;
        }

        public static bool operator >(Vec4 a, Vec4 b)
        {
            return a.LengthSquared > b.LengthSquared;
        }

        public static bool operator <=(Vec4 a, Vec4 b)
        {
            return a.LengthSquared <= b.LengthSquared;
        }

        public static bool operator >=(Vec4 a, Vec4 b)
        {
            return a.LengthSquared >= b.LengthSquared;
        }

        public double Dot(Vec4 rhs)
        {
            return x * rhs.x + y * rhs.y + z * rhs.z + w * rhs.w;
        }

        public void Clamp(double min, double max)
        {
            Util.Clamp(ref x, min, max);
            Util.Clamp(ref y, min, max);
            Util.Clamp(ref z, min, max);
            Util.Clamp(ref w, min, max);
        }

        public Vec3 ToVec3()
        {
            return new Vec3(this) / w;
        }

        public override string ToString()
        {
            return string.Format("Vec4({0},{1},{2},{3})", x, y, z, w);
        }

        public bool IsEquivalent(Vec4 v)
        {
            if (v == null) return false;
            return Util.IsEquivalent(x, v.x) && Util.IsEquivalent(y, v.y) && Util.IsEquivalent(z, v.z) && Util.IsEquivalent(w, v.w);
        }

        public override bool Equals(object obj)
        {
            return (obj != null) && (obj is Vec4) && ((Vec4)obj) == this;
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode();
        }
    }
}
